package com.xwj.service.impl;

import com.xwj.dao.UserAccountMapper;
import com.xwj.dao.UserAccountRecordMapper;
import com.xwj.domain.UserAccount;
import com.xwj.domain.UserAccountRecord;
import com.xwj.dto.UserAccountDto;
import com.xwj.service.RedisLockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author xwj
 * @date 2020/4/29
 */
@Service
public class RedisLockServiceImpl implements RedisLockService {
    private static Logger logger = LoggerFactory.getLogger(RedisLockServiceImpl.class);
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserAccountMapper userAccountMapper;
    @Autowired
    private UserAccountRecordMapper userAccountRecordMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void takeMoneyRedisLock(UserAccountDto dto) throws Exception {
        final String key = "takeMoneyRedisLock:" + dto.getAmount();
        // 保证value值的唯一性，防止误解锁
        final String value = UUID.randomUUID().toString();
        ValueOperations operations = redisTemplate.opsForValue();
        boolean result = false;
        try {
            // 调用SET K V NX PX操作获得锁
            // 设置了过期时间，即使锁的持有者后续发生崩溃而没有解锁，锁也会因为到了过期时间而自动解锁（即key被删除），不会发生死锁
            result = operations.setIfAbsent(key, value, 5L, TimeUnit.SECONDS);
            if (result) {
                UserAccount userAccount = userAccountMapper.selectByUserId(dto.getUserId());
                if (userAccount != null) {
                    // 余额足够
                    if (userAccount.getAmount().compareTo(dto.getAmount()) >= 0) {
                        // 更新余额
                        userAccountMapper.updateAmount(dto.getAmount(), userAccount.getId());
                        // 记录
                        UserAccountRecord userAccountRecord = new UserAccountRecord();
                        userAccountRecord.setCreateTime(LocalDateTime.now());
                        userAccountRecord.setAccountId(userAccount.getId());
                        userAccountRecord.setMoney(dto.getAmount());
                        userAccountRecordMapper.insertSelective(userAccountRecord);
                        logger.info("用户：{}，本次提现：{}，当前余额：{}", dto.getUserId(), dto.getAmount(), userAccount.getAmount());
                    } else {
                        throw new Exception("账户余额不足");
                    }
                } else {
                    throw new Exception("账户不存在");
                }
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (result) {
                // 释放锁，使用lua脚本执行删除命令，并进行value的对比，保证value的唯一性，防止误解锁
                String release_lock_lua_script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(release_lock_lua_script, Long.class);
                redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singletonList(value));
            }
        }
    }
}
